package thumb

import (
	"bytes"
	"github.com/BurntSushi/graphics-go/graphics"
	"github.com/boltdb/bolt"
	"github.com/pkg/errors"
	"image"
	"image/jpeg"
	"io"
)

var (
	KeyBucketThumb = []byte("THUMB")
)

const (
	defaultThumbSize = 128
)

/*
缩略图存储
write: 将指定文件生成缩略图并写入存储 暂时只写入一个缩略图
Read:  读取指定key 的数据
WriteThumb 将指定的数据与key 存入
*/
type Storage interface {
	Write(io.Reader, string) error
	WriteThumb([]byte, string) error
	Read(string) ([]byte, error)
}

var glStorage Storage

type BoltThumbStorage struct {
	db *bolt.DB
}

func openBoltThumbStorage(path string) error {
	var storage = &BoltThumbStorage{}
	db, err := bolt.Open(path, 0600, nil)
	if err != nil {
		return errors.Wrap(err, "open thumb database failed")
	}

	storage.db = db
	glStorage = storage
	return nil
}

func Instance() Storage {
	return glStorage
}

func (m *BoltThumbStorage) WriteThumb(d []byte, k string) error {
	var err error
	return m.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(KeyBucketThumb)
		if bucket == nil {
			bucket, err = tx.CreateBucket(KeyBucketThumb)
			if err != nil {
				return err
			}
		}

		return bucket.Put([]byte(k), d)
	})
}

func (m *BoltThumbStorage) Write(reader io.Reader, k string) error {
	data, err := thumbImage(reader, defaultThumbSize, resizeThumb)
	if err != nil {
		return errors.Wrap(err, "thumb image failed")
	}

	return m.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(KeyBucketThumb)
		if bucket == nil {
			bucket, err = tx.CreateBucket(KeyBucketThumb)
			if err != nil {
				return err
			}
		}

		return bucket.Put([]byte(k), data)
	})
}

func (m *BoltThumbStorage) Read(k string) ([]byte, error) {
	var out []byte
	err := m.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(KeyBucketThumb)
		if bucket == nil {
			return nil
		}
		out = bucket.Get([]byte(k))
		return nil
	})

	return out, err
}

type thumbMethod func(image.Image, int) image.Image

/*
针对图片文件 data，以及尺寸 生成缩略图
默认缩略图尺寸 256，若原图尺寸小于该值，则直接使用原图尺寸生成一份 jpeg
如果源文件尺寸 小于 size，则使用 默认缩略图尺寸
尺寸比较 必须是 长与宽 均小于，最终生成的缩略图应为正方形，若原图尺寸为非正方形，则需要缩放-裁切
裁剪应【居中裁切】
*/
func thumbImage(reader io.Reader, size int, f thumbMethod) ([]byte, error) {
	src, format, err := image.Decode(reader)
	if err != nil {
		return nil, errors.Wrap(err, "decode picture failed")
	}
	println("picture is ", format)

	dst := f(src, size)
	if dst == nil {
		return nil, errors.Wrap(err, "resize and thumb failed")
	}

	buf := new(bytes.Buffer)
	err = jpeg.Encode(buf, dst, &jpeg.Options{Quality: 60})
	if err != nil {
		return nil, errors.Wrap(err, "encode image to jpeg failed")
	}

	return buf.Bytes(), nil
}

func resizeThumb(src image.Image, size int) image.Image {
	return scale(src, size)
}

func resizeAndCutThumb(src image.Image, size int) image.Image {
	scaled := scale(src, size)
	oX := scaled.Bounds().Max.X
	oY := scaled.Bounds().Max.Y
	var r image.Rectangle

	if oX > oY {
		// 横屏图片
		r = image.Rect((oX-oY)/2, 0, (oX-oY)/2+oY, oY)
	} else {
		// 竖屏图片
		r = image.Rect(0, (oY-oX)/2, oX, (oY-oX)/2+oX)
	}

	var subImg image.Image
	switch rgbImg := scaled.(type) {
	case *image.YCbCr:
		subImg = rgbImg.SubImage(r).(*image.YCbCr)
	case *image.RGBA:
		subImg = rgbImg.SubImage(r).(*image.RGBA)
	case *image.NRGBA:
		subImg = rgbImg.SubImage(r).(*image.NRGBA)
	default:
		subImg = nil
	}

	return subImg
}

// 等比例缩放
func scale(origin image.Image, size int) image.Image {
	if size == 0 {
		panic("width or height must not be zero")
	}

	oX := origin.Bounds().Max.X
	oY := origin.Bounds().Max.Y
	dX := 0
	dY := 0
	if oX > size && oY > size {
		// 缩
		if oX > oY {
			dY = size
			dX = size * oX / oY
		} else {
			dX = size
			dY = size * oY / oX
		}
	} else {
		// 放
		if oX > oY {
			dY = size
			dX = size * oX / oY
			//dX = size
			//dY = size * oY / oX
		} else {
			dY = size
			dX = size * oX / oY
		}
	}

	dst := image.NewRGBA(image.Rect(0, 0, dX, dY))
	err := graphics.Scale(dst, origin)
	if err != nil {
		println("graphics scale failed")
		return nil
	}

	return dst
}
